package cn.kstry.flux.demo.config.diagram;

import cn.kstry.flux.demo.common.constant.CSF;
import cn.kstry.flux.demo.service.*;
import cn.kstry.framework.core.component.bpmn.builder.SubProcessLink;
import cn.kstry.framework.core.component.bpmn.joinpoint.InclusiveJoinPoint;
import cn.kstry.framework.core.component.bpmn.joinpoint.ParallelJoinPoint;
import cn.kstry.framework.core.component.bpmn.link.ProcessLink;
import cn.kstry.framework.core.component.bpmn.link.StartProcessLink;
import cn.kstry.framework.core.util.KeyUtil;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 举例说明代码方式也可以定义比较复杂的流程。但是可读性很差
 * 简单的流程可以用代码来定义，复杂的流程还是推荐使用 bpmn 文件定义，可视化展现
 * 还有一种方式就是 bpmn 文件可视化定义一些复杂的子流程，在代码中进行引用，这个是完全可行的，配置文件和代码中子流程的相互引用是打通了的
 * <p>
 * 定义bpmn可视化流程图（当前类代码定义与bpmn定义等效）：
 *      <a href="https://gitee.com/kstry/kstry-flux-demo/blob/master/kstry-flux-demo-common/src/main/resources/img/goods-show-demo-flow.png">查看图片</a>
 */
@Configuration
public class ProcessDiagramConfiguration {

    /**
     * 构建统计子流程
     *
     * @return 统计子流程
     */
    @Bean
    public SubProcessLink buildStatisticsSubProcess() {
        return SubProcessLink.build(ProcessDiagramConfiguration::buildStatisticsSubProcess, link -> {

            // 构建一个游离的包含网关，网关标识开启异步
            InclusiveJoinPoint inclusive1 = link.inclusive().openAsync().build();

            // 开始节点指向包含网关
            link.nextInclusive(inclusive1);

            // 再次构建一个游离的包含网关
            InclusiveJoinPoint inclusive2 = link.inclusive().build();

            // 包含网关1分别指向三个 TaskService， 三个 TaskService 再分别指向包含网关2，包含网关2指向结束节点
            inclusive2.joinLinks(
                    inclusive1.nextService("res.needEvaluate == true", EvaluationService::getEvaluationInfo).build(),
                    inclusive1.nextService(GoodsService::getGoodsExtInfo).build(),
                    inclusive1.nextService(OrderService::getOrderInfo).build()
            ).end();
        });
    }

    @Bean
    public ProcessLink buildShowGoodsLink() {
        StartProcessLink bpmnLink = StartProcessLink.build(ProcessDiagramConfiguration::buildShowGoodsLink, "展示商品详情");

        // 构建一个游离的并行网关，网关标识开启异步，并且是非严格模式的并行网关（因为网关左侧的分支并不能全部到达并行网关，如果不设置非严格模式就会报错，也可用包含网关来替代将不会有这个问题）
        ParallelJoinPoint pPoint1 = bpmnLink.parallel().openAsync().notStrictMode().build();

        // 初始化基本信息
        ProcessLink initTask = bpmnLink.nextService(GoodsService::initBaseInfo).customRole("goods-custom-role@goods-detail").build();

        // 指向并行网关
        initTask.nextParallel("res.img == null", pPoint1);

        ProcessLink rCheck = initTask
                // res.img != null 时校验图片
                .nextService("res.img != null", RiskControlService::checkImg).build()
                // 指向一个判断角色的排他网关
                .nextExclusive().build();

        // 排他网关指向并行网关
        rCheck.nextParallel("!r:check-img@triple", pPoint1);

        rCheck
                // 三方服务统计
                .nextService("r:check-img@triple", RiskControlService::statistics).build()
                // 之后指向并行网关
                .nextParallel(pPoint1);


        // 指向排他网关
        ProcessLink sourceCheck = pPoint1.nextExclusive().build();

        // 构建一个游离的包含网关
        InclusiveJoinPoint iPoint1 = bpmnLink.inclusive().build();

        // 排他网关指向包含网关
        sourceCheck.nextInclusive(KeyUtil.req("source", "!=", "'app'"), iPoint1);

        // 排他网关指向送运费险节点
        sourceCheck
                // 送运费险有可能报错，设置非严格模式，报错会忽略，会继续向下执行
                .nextService(LogisticService::getLogisticInsurance).notStrictMode().build()
                .nextInclusive(iPoint1);

        bpmnLink.parallel().build()
                .joinLinks(
                        // 并行网关指向广告服务节点，广告服务节点没有对应的TaskService默认会报错，但是加上 allowAbsent() 之后就不会报错了
                        pPoint1.nextTask("advertising", "get-advertising").name("获取广告").allowAbsent().build(),

                        // 并行节点指向子流程
                        pPoint1.nextSubProcess(ProcessDiagramConfiguration::buildStatisticsSubProcess).build(),

                        // 并行网关指向 加载SKU信息 节点
                        pPoint1.nextService(GoodsService::initSku).build(),

                        iPoint1.nextService(ShopService::getShopInfoByGoodsId).build()
                )
                .nextService(GoodsService::detailPostProcess).build()
                .end();
        return bpmnLink;
    }
}
